package com.gapp.king.web.client;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Date;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.http.client.URL;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.user.client.Random;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;

import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;

/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */
public class Gappweb implements EntryPoint {
	/**
	 * The message displayed to the user when the server cannot be reached or
	 * returns an error.
	 */
	private static final String SERVER_ERROR = "An error occurred while "
			+ "attempting to contact the server. Please check your network "
			+ "connection and try again.";

	/**
	 * Create a remote service proxy to talk to the server-side Greeting service.
	 */
	private final GreetingServiceAsync greetingService = GWT
			.create(GreetingService.class);

	private static final int REFRESH_INTERVAL = 5000; // ms
	private VerticalPanel mainPanel = new VerticalPanel();
	private FlexTable stocksFlexTable = new FlexTable();
    private HorizontalPanel addPanel = new HorizontalPanel();
    private TextBox newSymbolTextBox = new TextBox();
    private Button addStockButton = new Button("Add");
    private Label lastUpdatedLabel = new Label();
    private List<String> stocks = new ArrayList<String>();
    private GappWebConstants constants = GWT.create(GappWebConstants.class);
    private GappWebMessages messages = GWT.create(GappWebMessages.class);
    private GappWebServiceAsync stockPriceSvc = GWT.create(GappWebService.class);
    private Label errorMsgLabel = new Label();
    private static final String JSON_URL = GWT.getModuleBaseURL() + "jsonStockPrices?q=";
    //private static final String JSON_URL = GWT.getModuleBaseURL() + "BAD?q=";
    
    private LoginInfo loginInfo = null;
    private VerticalPanel loginPanel = new VerticalPanel();
    private Label loginLabel = new Label("Please Sign In with your Google Account!");
    private Anchor signInLink = new Anchor("Sign In");
    private Anchor signOutLink = new Anchor("Sign Out");
    
    private final StockServiceAsync stockService = GWT.create(StockService.class);
	/**
	 * This is the entry point method.
	 */
    
    public void onModuleLoad() 
    {
    	LoginServiceAsync loginService = GWT.create(LoginService.class);
    	loginService.login(GWT.getHostPageBaseURL(), new AsyncCallback<LoginInfo>() {
    		public void onFailure(Throwable error) {
    	      
    		}
    		
    		public void onSuccess(LoginInfo result) {
    	        loginInfo = result;
    	        if(loginInfo.isLoggedIn()) {
    	           loadGappWeb();
    	        } else {
    	          loadLogin();
    	        }
    	      }
    	});    	          	
    }
    
    private void loadLogin() {
        // Assemble login panel.
        signInLink.setHref(loginInfo.getLoginURL());
        loginPanel.add(loginLabel);
        loginPanel.add(signInLink);
        RootPanel.get("stockList").add(loginPanel);
     }
    
	public void loadGappWeb() {
		
		signOutLink.setHref(loginInfo.getLogoutURL());
		
		Window.setTitle(constants.stockWatcher());
		RootPanel.get("appTitle").add(new Label(constants.stockWatcher()));
		addStockButton = new Button(constants.add());
		
		//add the stock table
		stocksFlexTable.setText(0, 0, constants.symbol());
		stocksFlexTable.setText(0, 1, constants.price());
		stocksFlexTable.setText(0, 2, constants.change());
		stocksFlexTable.setText(0, 3, constants.remove());
		
		//stocksFlexTable.setCellPadding(6);
		
		//set the style css sheet
		stocksFlexTable.getRowFormatter().addStyleName(0, "stockListHeader");
		stocksFlexTable.addStyleName("stockList");
		stocksFlexTable.getCellFormatter().addStyleName(0, 1, "stockListNumericColumn");
	    stocksFlexTable.getCellFormatter().addStyleName(0, 2, "stockListNumericColumn");
	    stocksFlexTable.getCellFormatter().addStyleName(0, 3, "stockListRemoveColumn");
	   
		//assemble the text box and button
		addPanel.add(newSymbolTextBox);
		addPanel.add(addStockButton);
		addPanel.addStyleName("addPanel");
		
		//initial the error message
		errorMsgLabel.setStyleName("errorMessage");
		errorMsgLabel.setVisible(false);
		
		//assemble the main panel
		signOutLink.setVisible(true);
		mainPanel.add(errorMsgLabel);
		mainPanel.add(signOutLink);
		mainPanel.add(stocksFlexTable);
		mainPanel.add(addPanel);
		mainPanel.add(lastUpdatedLabel);
		
		RootPanel.get("stockList").add(mainPanel);
		
		newSymbolTextBox.setFocus(true);
		
		Timer refreshTimer = new Timer() {
		      @Override
		      public void run() {
		        //refreshWatchList();
		        refreshWatchListAsync();
		    	//refreshWatchListJson();
		      }
		    };
	    refreshTimer.scheduleRepeating(REFRESH_INTERVAL);

		
		// Listen for mouse events on the Add button.
	    addStockButton.addClickHandler(new ClickHandler() {
	      public void onClick(ClickEvent event) {
	        addStock();
	      }
	    });
	    
	    //also need listen to the keyboard enter events
	    newSymbolTextBox.addKeyPressHandler(new KeyPressHandler() {
	    	public void onKeyPress( KeyPressEvent event )
	    	{
	    		if ( event.getCharCode() == KeyCodes.KEY_ENTER )
	    		{
	    			addStock();
	    		}
	    	}
	    });
		
	}
	
    /**
     * Add stock to FlexTable. Executed when the user clicks the addStockButton or
     * presses enter in the newSymbolTextBox.
     */
    private void addStock() {
      // TODO Auto-generated method stub
      // check the input value
      final String symbol = newSymbolTextBox.getText().toUpperCase().trim();
      newSymbolTextBox.setFocus(true);
      
      if ( !symbol.matches("^[0-9A-Z\\.]{1,10}$"))
      {
    	  //Window.alert("'" + symbol + "' is not a valid symbol.");
    	  Window.alert(messages.invalidSymbol(symbol));
    	  newSymbolTextBox.selectAll();
    	  return;
      }
      
      newSymbolTextBox.setText("");
      
      //just return if there are duplicate symbol
      if (stocks.contains(symbol))
          return;
      
      int row = stocksFlexTable.getRowCount();
      stocks.add(symbol);
      stocksFlexTable.setText(row, 0, symbol);
      stocksFlexTable.setWidget(row, 2, new Label());
      stocksFlexTable.addStyleName("stockList");
   	  stocksFlexTable.getCellFormatter().addStyleName(row, 1, "stockListNumericColumn");
      stocksFlexTable.getCellFormatter().addStyleName(row, 2, "stockListNumericColumn");
      stocksFlexTable.getCellFormatter().addStyleName(row, 3, "stockListRemoveColumn");
	      
      Button removeStockButton = new Button("x");
      removeStockButton.addStyleDependentName("remove");
      removeStockButton.addClickHandler(new ClickHandler() {
        public void onClick(ClickEvent event) {
          int removedIndex = stocks.indexOf(symbol);
          stocks.remove(removedIndex);
          stocksFlexTable.removeRow(removedIndex + 1);
        }
      });
      stocksFlexTable.setWidget(row, 3, removeStockButton);
      
      //refresh the list after added the stock
      //refreshWatchList();
      refreshWatchListAsync();
      //refreshWatchListJson();

    }
    
    private void refreshWatchListJson()
    {
    	if (stocks.size() == 0)
    	{
    		return;
    	}
    	
    	String url = JSON_URL;
    	
    	// Append watch list stock symbols to query URL.
        Iterator iter = stocks.iterator();
        while (iter.hasNext()) {
          url += iter.next();
          if (iter.hasNext()) {
            url += "+";
          }
        }
        
        url = URL.encode(url);
        
        RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, url);

        try {
          Request request = builder.sendRequest(null, new RequestCallback() {
            public void onError(Request request, Throwable exception) {
              displayError("Couldn't retrieve JSON");
            }

            public void onResponseReceived(Request request, Response response) {
              if (200 == response.getStatusCode()) {
                updateTableJson(asArrayOfStockData(response.getText()));
              } else {
                displayError("Couldn't retrieve JSON (" + response.getStatusText()
                    + ")");
              }
            }
          });
        } catch (RequestException e) {
          displayError("Couldn't retrieve JSON");
        }
    }
    
    public void updateTableJson(JsArray<StockData> prices)
    {
    	for (int i = 0; i < prices.length(); i++) 
    	{
    		updateTablePrice(prices.get(i));
    	
    	}

    	    // Display timestamp showing last refresh.
       lastUpdatedLabel.setText("Last update : "
    	        + DateTimeFormat.getMediumDateTimeFormat().format(new Date()));
       
       errorMsgLabel.setVisible(false);
    }
    
    public void updateTablePrice(StockData price)
    {
    	if (!stocks.contains(price.getSymbol())) 
   		{
   		      return;
   	    }
	
    	int row = stocks.indexOf(price.getSymbol()) + 1;

        // Format the data in the Price and Change fields.
        String priceText = NumberFormat.getFormat("#,##0.00").format(
            price.getPrice());
        NumberFormat changeFormat = NumberFormat.getFormat("+#,##0.00;-#,##0.00");
        String changeText = changeFormat.format(price.getChange());
        String changePercentText = changeFormat.format(price.getChangePercent());

        // Populate the Price and Change fields with new data.
        stocksFlexTable.setText(row, 1, priceText);
        //stocksFlexTable.setText(row, 2, changeText + " (" + changePercentText
        //    + "%)");
        Label changeWidget = (Label)stocksFlexTable.getWidget(row, 2);
        changeWidget.setText(changeText + " (" + changePercentText + "%)");
        
     // Change the color of text in the Change field based on its value.
        String changeStyleName = "noChange";
        if (price.getChangePercent() < -0.1f) {
          changeStyleName = "negativeChange";
        }
        else if (price.getChangePercent() > 0.1f) {
          changeStyleName = "positiveChange";
        }

        changeWidget.setStyleName(changeStyleName);
    }
    
    private void refreshWatchList()
    {
    	final double MAX_PRICE = 100.0; // $100.00
        final double MAX_PRICE_CHANGE = 0.02; // +/- 2%

        List<StockPrice> prices = new ArrayList<StockPrice>();
        for (int i = 0; i < stocks.size(); i++) 
        {
          double price = Random.nextDouble() * MAX_PRICE;
          double change = price * MAX_PRICE_CHANGE
              * (Random.nextDouble() * 2.0 - 1.0);

          prices.add(new StockPrice(stocks.get(i), price, change));
        }

        updateTable(prices);
    }
    
    private void refreshWatchListAsync()
    {
    	if ( stockPriceSvc == null)
    	{
    		stockPriceSvc = GWT.create(GappWebService.class);
    	}
    	
    	// Set up the callback object.
        AsyncCallback<List<StockPrice>> callback = new AsyncCallback<List<StockPrice>>() {
        	public void onFailure(Throwable caught) {
            // TODO: Do something with errors.
        		String details = caught.getMessage();
        		if ( caught instanceof DelistedException )
        		{
        			details = "Company '" + ((DelistedException)caught).getSymbol() + "' was delisted";
        		}
        		errorMsgLabel.setText("Errors : " + details);
        		errorMsgLabel.setVisible(true);
        	}

        	public void onSuccess(List<StockPrice> result) {
        		updateTable(result);
        	}
        };
        
     // Make the call to the stock price service.
        stockPriceSvc.getPrices(stocks.toArray(new String[0]), callback);
    }
    
    private void updateTable(List<StockPrice> prices)
    {
    	Iterator it = prices.iterator();
    	
    	while(it.hasNext())
    	{
    		StockPrice price = (StockPrice)it.next();
    		
	    	if (!stocks.contains(price.getSymbol())) 
	   		{
	   		      return;
	   	    }
    	
	    	int row = stocks.indexOf(price.getSymbol()) + 1;
	
	        // Format the data in the Price and Change fields.
	        String priceText = NumberFormat.getFormat("#,##0.00").format(
	            price.getPrice());
	        NumberFormat changeFormat = NumberFormat.getFormat("+#,##0.00;-#,##0.00");
	        String changeText = changeFormat.format(price.getChange());
	        String changePercentText = changeFormat.format(price.getChangePercent());
	
	        // Populate the Price and Change fields with new data.
	        stocksFlexTable.setText(row, 1, priceText);
	        //stocksFlexTable.setText(row, 2, changeText + " (" + changePercentText
	        //    + "%)");
	        Label changeWidget = (Label)stocksFlexTable.getWidget(row, 2);
	        changeWidget.setText(changeText + " (" + changePercentText + "%)");
	        
	     // Change the color of text in the Change field based on its value.
	        String changeStyleName = "noChange";
	        if (price.getChangePercent() < -0.1f) {
	          changeStyleName = "negativeChange";
	        }
	        else if (price.getChangePercent() > 0.1f) {
	          changeStyleName = "positiveChange";
	        }

	        changeWidget.setStyleName(changeStyleName);
    	}
    	
    	// Display timestamp showing last refresh.
        //lastUpdatedLabel.setText("Last update : "
          //  + DateTimeFormat.getMediumDateTimeFormat().format(new Date()));
        lastUpdatedLabel.setText(messages.lastUpdate(new Date()));
        
        errorMsgLabel.setVisible(false);
    }
    
    /**
     * Convert the string of JSON into JavaScript object.
     */
    private final native JsArray<StockData> asArrayOfStockData(String json) /*-{
      return eval(json);
    }-*/;
    
    /**
     * If can't get JSON, display error message.
     * @param error
     */
    private void displayError(String error) {
      errorMsgLabel.setText("Error: " + error);
      errorMsgLabel.setVisible(true);
    }
}
